//===--- PILGenDynamicCast.cpp - PILGen for dynamic casts -----------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#include "polarphp/pil/gen/PILGenDynamicCast.h"
#include "polarphp/pil/gen/Initialization.h"
#include "polarphp/pil/gen/RValue.h"
#include "polarphp/pil/gen/Scope.h"
#include "polarphp/pil/gen/ExitableFullExpr.h"
#include "polarphp/pil/lang/DynamicCasts.h"
#include "polarphp/pil/lang/PILArgument.h"
#include "polarphp/pil/lang/TypeLowering.h"

using namespace polar;
using namespace lowering;

namespace {
class CheckedCastEmitter {
   PILGenFunction &SGF;
   PILLocation Loc;
   CanType SourceType;
   CanType TargetType;

   enum class CastStrategy : uint8_t {
      Address,
      Scalar,
   };
   CastStrategy Strategy;

public:
   CheckedCastEmitter(PILGenFunction &SGF, PILLocation loc,
                      Type sourceType, Type targetType)
      : SGF(SGF), Loc(loc), SourceType(sourceType->getCanonicalType()),
        TargetType(targetType->getCanonicalType()),
        Strategy(computeStrategy()) {
   }

   bool isOperandIndirect() const {
      return Strategy == CastStrategy::Address;
   }

   ManagedValue emitOperand(Expr *operand) {
      AbstractionPattern mostGeneral = SGF.SGM.Types.getMostGeneralAbstraction();
      auto &origSourceTL = SGF.getTypeLowering(mostGeneral, SourceType);

      SGFContext ctx;

      std::unique_ptr<TemporaryInitialization> temporary;
      if (isOperandIndirect() && SGF.silConv.useLoweredAddresses()) {
         temporary = SGF.emitTemporary(Loc, origSourceTL);
         ctx = SGFContext(temporary.get());
      }

      auto result = SGF.emitRValueAsOrig(operand, mostGeneral,
                                         origSourceTL, ctx);

      if (isOperandIndirect() && SGF.silConv.useLoweredAddresses()) {
         // Force the result into the temporary if it's not already there.
         if (!result.isInContext()) {
            result.forwardInto(SGF, Loc, temporary->getAddress());
            temporary->finishInitialization(SGF);
         }
         return temporary->getManagedAddress();
      }

      return result;
   }

   RValue emitUnconditionalCast(ManagedValue operand, SGFContext ctx) {
      // The cast functions don't know how to work with anything but
      // the most general possible abstraction level.
      AbstractionPattern abstraction = SGF.SGM.Types.getMostGeneralAbstraction();
      auto &origTargetTL = SGF.getTypeLowering(abstraction, TargetType);
      auto &substTargetTL = SGF.getTypeLowering(TargetType);
      bool hasAbstraction =
         (origTargetTL.getLoweredType() != substTargetTL.getLoweredType());

      // If we're using checked_cast_addr, take the operand (which
      // should be an address) and build into the destination buffer.
      if (Strategy == CastStrategy::Address &&
          SGF.silConv.useLoweredAddresses()) {
         PILValue resultBuffer =
            createAbstractResultBuffer(hasAbstraction, origTargetTL, ctx);
         SGF.B.createUnconditionalCheckedCastAddr(Loc,
                                                  operand.forward(SGF), SourceType,
                                                  resultBuffer, TargetType);
         return RValue(SGF, Loc, TargetType,
                       finishFromResultBuffer(hasAbstraction, resultBuffer,
                                              abstraction, origTargetTL, ctx));
      }

      ManagedValue result;
      if (Strategy == CastStrategy::Address) {
         result = SGF.B.createUnconditionalCheckedCastValue(
            Loc, operand, SourceType,
            origTargetTL.getLoweredType(), TargetType);
      } else {
         result = SGF.B.createUnconditionalCheckedCast(
            Loc, operand,
            origTargetTL.getLoweredType(), TargetType);
      }

      return RValue(SGF, Loc, TargetType,
                    finishFromResultScalar(hasAbstraction, result,
                                           CastConsumptionKind::TakeAlways,
                                           abstraction, origTargetTL, ctx));
   }

   /// Emit a conditional cast.
   void emitConditional(
      ManagedValue operand, CastConsumptionKind consumption, SGFContext ctx,
      llvm::function_ref<void(ManagedValue)> handleTrue,
      llvm::function_ref<void(Optional<ManagedValue>)> handleFalse,
      ProfileCounter TrueCount = ProfileCounter(),
      ProfileCounter FalseCount = ProfileCounter()) {
      // The cast instructions don't know how to work with anything
      // but the most general possible abstraction level.
      AbstractionPattern abstraction =
         SGF.SGM.Types.getMostGeneralAbstraction();
      auto &origTargetTL = SGF.getTypeLowering(abstraction, TargetType);
      auto &substTargetTL = SGF.getTypeLowering(TargetType);
      bool hasAbstraction =
         (origTargetTL.getLoweredType() != substTargetTL.getLoweredType());

      PILBasicBlock *falseBB = SGF.B.splitBlockForFallthrough();
      PILBasicBlock *trueBB = SGF.B.splitBlockForFallthrough();

      // Emit the branch.
      ManagedValue operandValue;
      PILValue resultBuffer;
      if (Strategy == CastStrategy::Address &&
          SGF.silConv.useLoweredAddresses()) {
         assert(operand.getType().isAddress());
         resultBuffer =
            createAbstractResultBuffer(hasAbstraction, origTargetTL, ctx);
         SGF.B.createCheckedCastAddrBranch(
            Loc, consumption, operand.forward(SGF), SourceType, resultBuffer,
            TargetType, trueBB, falseBB, TrueCount, FalseCount);
      } else if (Strategy == CastStrategy::Address) {
         // Opaque value mode
         operandValue = std::move(operand);
         SGF.B.createCheckedCastValueBranch(
            Loc, operandValue, SourceType,
            origTargetTL.getLoweredType(), TargetType,
            trueBB, falseBB);
      } else {
         // Tolerate being passed an address here.  It comes up during switch
         // emission.
         operandValue = std::move(operand);
         if (operandValue.getType().isAddress()) {
            operandValue = SGF.B.createLoadTake(Loc, operandValue);
         }

         // If we are not supposed to destroy this value on failure, then we need
         // to borrow it.
         if (!shouldDestroyOnFailure(consumption)) {
            operandValue = operandValue.borrow(SGF, Loc);
         }
         SGF.B.createCheckedCastBranch(Loc, /*exact*/ false, operandValue,
                                       origTargetTL.getLoweredType(), TargetType,
                                       trueBB, falseBB, TrueCount, FalseCount);
      }

      // Emit the success block.
      SGF.B.setInsertionPoint(trueBB);
      {
         FullExpr scope(SGF.Cleanups, CleanupLocation::get(Loc));

         ManagedValue result;
         if (Strategy == CastStrategy::Address &&
             SGF.silConv.useLoweredAddresses()) {
            result = finishFromResultBuffer(hasAbstraction, resultBuffer,
                                            abstraction, origTargetTL, ctx);
         } else {
            // If we had copy_on_success, then we need to use a guaranteed
            // argument.
            ManagedValue argument;
            if (!shouldTakeOnSuccess(consumption)) {
               argument = SGF.B.createGuaranteedPhiArgument(
                  origTargetTL.getLoweredType());
            } else {
               argument =
                  SGF.B.createOwnedPhiArgument(origTargetTL.getLoweredType());
            }
            result = finishFromResultScalar(hasAbstraction, argument, consumption,
                                            abstraction, origTargetTL, ctx);
         }

         handleTrue(result);
         assert(!SGF.B.hasValidInsertionPoint() && "handler did not end block");
      }

      // Emit the failure block.
      SGF.B.setInsertionPoint(falseBB);
      {
         FullExpr scope(SGF.Cleanups, CleanupLocation::get(Loc));

         // If we have an address only type, do not handle the consumption
         // rules. These are handled for us by the user.
         if (Strategy == CastStrategy::Address) {
            handleFalse(None);
            assert(!SGF.B.hasValidInsertionPoint() &&
                   "handler did not end block");
            return;
         }

         // Otherwise, we use the following strategy:
         //
         // 1. If we have a take_always, we create a phi node argument for the
         // failure case and a scope for that so that it is immediately
         // destroyed.
         //
         // 2. If we have a take_on_success or copy_on_success, then on failure,
         // we propagate through the default argument, but do not clean it up. On
         // the false case, our user must treat the taken value as a new value.
         if (shouldDestroyOnFailure(consumption)) {
            {
               FullExpr argScope(SGF.Cleanups, CleanupLocation::get(Loc));
               SGF.B.createOwnedPhiArgument(operandValue.getType());
            }
            handleFalse(None);
            assert(!SGF.B.hasValidInsertionPoint() &&
                   "handler did not end block");
            return;
         }

         switch (consumption) {
            case CastConsumptionKind::BorrowAlways:
            case CastConsumptionKind::CopyOnSuccess:
               SGF.B.createGuaranteedPhiArgument(operandValue.getType());
               handleFalse(None);
               break;
            case CastConsumptionKind::TakeAlways:
            case CastConsumptionKind::TakeOnSuccess:
               handleFalse(SGF.B.createOwnedPhiArgument(operandValue.getType()));
               break;
         }

         assert(!SGF.B.hasValidInsertionPoint() && "handler did not end block");
      }
   }

   PILValue createAbstractResultBuffer(bool hasAbstraction,
                                       const TypeLowering &origTargetTL,
                                       SGFContext ctx) {
      if (!hasAbstraction) {
         if (auto address = ctx.getAddressForInPlaceInitialization(SGF, Loc))
            return address;
      }

      return SGF.emitTemporaryAllocation(Loc, origTargetTL.getLoweredType());
   }

   ManagedValue finishFromResultBuffer(bool hasAbstraction, PILValue buffer,
                                       AbstractionPattern abstraction,
                                       const TypeLowering &origTargetTL,
                                       SGFContext ctx) {
      if (!hasAbstraction) {
         if (ctx.finishInPlaceInitialization(SGF))
            return ManagedValue::forInContext();
      }

      ManagedValue result;
      if (!origTargetTL.isAddressOnly()) {
         result = SGF.emitLoad(Loc, buffer, origTargetTL, ctx, IsTake);
      } else {
         result = SGF.emitManagedBufferWithCleanup(buffer, origTargetTL);
      }

      if (hasAbstraction) {
         result =
            SGF.emitOrigToSubstValue(Loc, result, abstraction, TargetType, ctx);
      }
      return result;
   }

   /// Our cast succeeded and gave us this abstracted value.
   ManagedValue finishFromResultScalar(bool hasAbstraction, ManagedValue value,
                                       CastConsumptionKind consumption,
                                       AbstractionPattern abstraction,
                                       const TypeLowering &origTargetTL,
                                       SGFContext ctx) {
      ManagedValue result = value;
      // Copy the result if this is copy-on-success.
      if (!shouldTakeOnSuccess(consumption))
         result = result.copy(SGF, Loc);

      // Re-abstract if necessary.
      if (hasAbstraction) {
         result =
            SGF.emitOrigToSubstValue(Loc, result, abstraction, TargetType, ctx);
      }

      return result;
   }

private:
   CastStrategy computeStrategy() const {
      if (canUseScalarCheckedCastInstructions(SGF.SGM.M, SourceType,
                                              TargetType))
         return CastStrategy::Scalar;
      return CastStrategy::Address;
   }
};
} // end anonymous namespace

void PILGenFunction::emitCheckedCastBranch(
   PILLocation loc, Expr *source, Type targetType, SGFContext ctx,
   llvm::function_ref<void(ManagedValue)> handleTrue,
   llvm::function_ref<void(Optional<ManagedValue>)> handleFalse,
   ProfileCounter TrueCount, ProfileCounter FalseCount) {
   CheckedCastEmitter emitter(*this, loc, source->getType(), targetType);
   ManagedValue operand = emitter.emitOperand(source);
   emitter.emitConditional(operand, CastConsumptionKind::TakeAlways, ctx,
                           handleTrue, handleFalse, TrueCount, FalseCount);
}

void PILGenFunction::emitCheckedCastBranch(
   PILLocation loc, ConsumableManagedValue src, Type sourceType,
   CanType targetType, SGFContext ctx,
   llvm::function_ref<void(ManagedValue)> handleTrue,
   llvm::function_ref<void(Optional<ManagedValue>)> handleFalse,
   ProfileCounter TrueCount, ProfileCounter FalseCount) {
   CheckedCastEmitter emitter(*this, loc, sourceType, targetType);
   emitter.emitConditional(src.getFinalManagedValue(), src.getFinalConsumption(),
                           ctx, handleTrue, handleFalse, TrueCount, FalseCount);
}

/// Emit a collection downcast expression.
///
/// \param conditional Whether to emit a conditional downcast; if
/// false, this will emit a forced downcast.
static RValue emitCollectionDowncastExpr(PILGenFunction &SGF,
                                         ManagedValue source,
                                         Type sourceType,
                                         PILLocation loc,
                                         Type destType,
                                         SGFContext C,
                                         bool conditional) {
   // Compute substitutions for the intrinsic call.
   auto fromCollection = sourceType->getCanonicalType();
   auto toCollection = destType->getCanonicalType();
   // Get the intrinsic function.
   auto &ctx = SGF.getAstContext();
   FuncDecl *fn = nullptr;
   if (fromCollection->getAnyNominal() == ctx.getArrayDecl()) {
      fn = conditional ? SGF.SGM.getArrayConditionalCast(loc)
                       : SGF.SGM.getArrayForceCast(loc);
   } else if (fromCollection->getAnyNominal() == ctx.getDictionaryDecl()) {
      fn = (conditional
            ? SGF.SGM.getDictionaryDownCastConditional(loc)
            : SGF.SGM.getDictionaryDownCast(loc));
   } else if (fromCollection->getAnyNominal() == ctx.getSetDecl()) {
      fn = (conditional
            ? SGF.SGM.getSetDownCastConditional(loc)
            : SGF.SGM.getSetDownCast(loc));
   } else {
      llvm_unreachable("unsupported collection upcast kind");
   }

   return SGF.emitCollectionConversion(loc, fn, fromCollection, toCollection,
                                       source, C);
}

static ManagedValue
adjustForConditionalCheckedCastOperand(PILLocation loc, ManagedValue src,
                                       CanType sourceType, CanType targetType,
                                       PILGenFunction &SGF) {
   // Reabstract to the most general abstraction, and put it into a
   // temporary if necessary.

   // Figure out if we need the value to be in a temporary.
   bool requiresAddress =
      !canUseScalarCheckedCastInstructions(SGF.SGM.M, sourceType, targetType);

   AbstractionPattern abstraction = SGF.SGM.M.Types.getMostGeneralAbstraction();
   auto &srcAbstractTL = SGF.getTypeLowering(abstraction, sourceType);

   bool hasAbstraction = (src.getType() != srcAbstractTL.getLoweredType());

   // Fast path: no re-abstraction required.
   if (!hasAbstraction &&
       (!requiresAddress ||
        (src.getType().isAddress() || !SGF.silConv.useLoweredAddresses()))) {
      return src;
   }

   std::unique_ptr<TemporaryInitialization> init;
   if (requiresAddress) {
      init = SGF.emitTemporary(loc, srcAbstractTL);

      if (hasAbstraction)
         src = SGF.emitSubstToOrigValue(loc, src, abstraction, sourceType);

      // Okay, if all we need to do is drop the value in an address,
      // this is easy.
      SGF.B.emitStoreValueOperation(loc, src.forward(SGF), init->getAddress(),
                                    StoreOwnershipQualifier::Init);
      init->finishInitialization(SGF);
      return init->getManagedAddress();
   }

   assert(hasAbstraction);
   assert(src.getType().isObject() &&
          "address-only type with abstraction difference?");

   // Produce the value at +1.
   return SGF.emitSubstToOrigValue(loc, src, abstraction, sourceType);
}


RValue lowering::emitUnconditionalCheckedCast(PILGenFunction &SGF,
                                              PILLocation loc,
                                              Expr *operand,
                                              Type targetType,
                                              CheckedCastKind castKind,
                                              SGFContext C) {
   // Handle collection downcasts directly; they have specific library
   // entry points.
   if (castKind == CheckedCastKind::ArrayDowncast ||
       castKind == CheckedCastKind::DictionaryDowncast ||
       castKind == CheckedCastKind::SetDowncast) {
      ManagedValue operandMV = SGF.emitRValueAsSingleValue(operand);
      return emitCollectionDowncastExpr(SGF, operandMV, operand->getType(), loc,
                                        targetType, C,
         /*conditional=*/false);
   }

   CheckedCastEmitter emitter(SGF, loc, operand->getType(),
                              targetType);
   ManagedValue operandValue = emitter.emitOperand(operand);
   return emitter.emitUnconditionalCast(operandValue, C);
}

RValue lowering::emitConditionalCheckedCast(
   PILGenFunction &SGF, PILLocation loc, ManagedValue operand,
   Type operandType, Type optTargetType, CheckedCastKind castKind,
   SGFContext C, ProfileCounter TrueCount, ProfileCounter FalseCount) {
   // Drill into the result type.
   CanType resultObjectType =
      optTargetType->getCanonicalType().getOptionalObjectType();
   assert(resultObjectType);

   // Handle collection downcasts directly; they have specific library
   // entry points.
   if (castKind == CheckedCastKind::ArrayDowncast ||
       castKind == CheckedCastKind::DictionaryDowncast ||
       castKind == CheckedCastKind::SetDowncast) {
      return emitCollectionDowncastExpr(SGF, operand, operandType, loc,
                                        resultObjectType, C,
         /*conditional=*/true);
   }

   operand = adjustForConditionalCheckedCastOperand(loc, operand,
                                                    operandType->getCanonicalType(),
                                                    resultObjectType, SGF);

   auto someDecl = SGF.getAstContext().getOptionalSomeDecl();
   auto &resultTL = SGF.getTypeLowering(optTargetType);

   // Set up a result buffer if desirable/required.
   PILValue resultBuffer;
   PILValue resultObjectBuffer;
   Optional<TemporaryInitialization> resultObjectTemp;
   SGFContext resultObjectCtx;
   if ((resultTL.isAddressOnly() && SGF.silConv.useLoweredAddresses()) ||
       (C.getEmitInto() && C.getEmitInto()->canPerformInPlaceInitialization())) {
      PILType resultTy = resultTL.getLoweredType();
      resultBuffer = SGF.getBufferForExprResult(loc, resultTy, C);
      resultObjectBuffer = SGF.B.createInitEnumDataAddr(
         loc, resultBuffer, someDecl,
         resultTy.getOptionalObjectType().getAddressType());
      resultObjectTemp.emplace(resultObjectBuffer, CleanupHandle::invalid());
      resultObjectCtx = SGFContext(&resultObjectTemp.getValue());
   }

   // Prepare a jump destination here.
   ExitableFullExpr scope(SGF, CleanupLocation::get(loc));

   auto operandCMV = ConsumableManagedValue::forOwned(operand);
   assert(operandCMV.getFinalConsumption() == CastConsumptionKind::TakeAlways);

   SGF.emitCheckedCastBranch(
      loc, operandCMV, operandType, resultObjectType, resultObjectCtx,
      // The success path.
      [&](ManagedValue objectValue) {
         // If we're not emitting into a temporary, just wrap up the result
         // in Some and go to the continuation block.
         if (!resultObjectTemp) {
            auto some = SGF.B.createEnum(loc, objectValue.forward(SGF), someDecl,
                                         resultTL.getLoweredType());
            SGF.Cleanups.emitBranchAndCleanups(scope.getExitDest(), loc, {some});
            return;
         }

         // Otherwise, make sure the value is in the context.
         if (!objectValue.isInContext()) {
            objectValue.forwardInto(SGF, loc, resultObjectBuffer);
         }
         SGF.B.createInjectEnumAddr(loc, resultBuffer, someDecl);
         SGF.Cleanups.emitBranchAndCleanups(scope.getExitDest(), loc);
      },
      // The failure path.
      [&](Optional<ManagedValue> Value) {
         // We always are performing a take here, so Value should be None since
         // the
         // object should have been destroyed immediately in the fail block.
         assert(!Value.hasValue() && "Expected a take_always consumption kind");
         auto noneDecl = SGF.getAstContext().getOptionalNoneDecl();

         // If we're not emitting into a temporary, just wrap up the result
         // in None and go to the continuation block.
         if (!resultObjectTemp) {
            auto none = SGF.B.createEnum(loc, nullptr, noneDecl,
                                         resultTL.getLoweredType());
            SGF.Cleanups.emitBranchAndCleanups(scope.getExitDest(), loc, {none});

            // Just construct the enum directly in the context.
         } else {
            SGF.B.createInjectEnumAddr(loc, resultBuffer, noneDecl);
            SGF.Cleanups.emitBranchAndCleanups(scope.getExitDest(), loc);
         }
      },
      TrueCount, FalseCount);

   // Enter the continuation block.
   PILBasicBlock *contBlock = scope.exit();

   ManagedValue result;
   if (resultObjectTemp) {
      result = SGF.manageBufferForExprResult(resultBuffer, resultTL, C);
   } else {
      auto argument = contBlock->createPhiArgument(resultTL.getLoweredType(),
                                                   ValueOwnershipKind::Owned);
      result = SGF.emitManagedRValueWithCleanup(argument, resultTL);
   }

   return RValue(SGF, loc, optTargetType->getCanonicalType(), result);
}

PILValue lowering::emitIsa(PILGenFunction &SGF, PILLocation loc,
                           Expr *operand, Type targetType,
                           CheckedCastKind castKind) {
   // Handle collection downcasts separately.
   if (castKind == CheckedCastKind::ArrayDowncast ||
       castKind == CheckedCastKind::DictionaryDowncast ||
       castKind == CheckedCastKind::SetDowncast) {
      ManagedValue operandMV = SGF.emitRValueAsSingleValue(operand);
      ManagedValue optValue = emitCollectionDowncastExpr(
         SGF, operandMV, operand->getType(), loc,
         targetType,
         SGFContext(), /*conditional=*/true)
         .getAsSingleValue(SGF, loc);

      // Materialize the input.
      PILValue optValueTemp;
      if (optValue.getType().isAddress()) {
         optValueTemp = optValue.forward(SGF);
      } else {
         optValueTemp = SGF.emitTemporaryAllocation(loc, optValue.getType());
         optValue.forwardInto(SGF, loc, optValueTemp);
      }

      return SGF.emitDoesOptionalHaveValue(loc, optValueTemp);
   }

   // Prepare a jump destination here.
   ExitableFullExpr scope(SGF, CleanupLocation::get(loc));

   auto i1Ty = PILType::getBuiltinIntegerType(1, SGF.getAstContext());

   // When we pass in an expr, we perform a take_always cast.
   SGF.emitCheckedCastBranch(
      loc, operand, targetType, SGFContext(),
      [&](ManagedValue value) {
         PILValue yes = SGF.B.createIntegerLiteral(loc, i1Ty, 1);
         SGF.Cleanups.emitBranchAndCleanups(scope.getExitDest(), loc, yes);
      },
      [&](Optional<ManagedValue> Value) {
         assert(!Value.hasValue() && "Expected take_always semantics");
         PILValue no = SGF.B.createIntegerLiteral(loc, i1Ty, 0);
         SGF.Cleanups.emitBranchAndCleanups(scope.getExitDest(), loc, no);
      });

   auto contBB = scope.exit();
   auto isa = contBB->createPhiArgument(i1Ty, ValueOwnershipKind::None);
   return isa;
}

